import React, {Component} from 'react'
import {MarkdownPreview} from 'react-marked-markdown';
import ReactMarkdown from 'react-markdown'
import Highlight from 'react-highlight'
// import py1_1 from '../static/py1_1.md'

class Py11 extends Component {
    constructor(props) {
        super(props);
        this.state = {
            value: `
一旦你开始采集网络数据，就会感受到浏览器为我们做的所有细节。网络上如果没有
HTML 文本格式层、 CSS 样式层、 JavaScript 执行层和图像渲染层，乍看起来会有点儿吓
人，但是在这一章和下一章，我们将介绍如何不通过浏览器的帮助来格式化和理解数据。

本章将首先向网络服务器发送 GET 请求以获取具体网页，再从网页中读取 HTML 内容，
最后做一些简单的信息提取，将我们要寻找的内容分离出来。

> 1.1　网络连接

如果你没在网络或网络安全上花过太多时间，那么互联网的原理可能看起来有点儿神秘。
准确地说，每当打开浏览器连接 http://google.com 的时候，我们不会思考网络正在做什么，
而且如今也不必思考。 实际上，我认为很神奇的是，计算机接口已经如此先进，让大多数
人上网的时候完全不思考网络是如何工作的。

但是，网络数据采集需要抛开一些接口的遮挡，不仅是在浏览器层（它如何解释所有的
HTML、 CSS 和 JavaScript），有时也包括网络连接层

我们通过下面的例子让你对浏览器获取信息的过程有一个基本的认识。 Alice 有一台网络
服务器。 Bob 有一个台式机正准备连接 Alice 的服务器。当一台机器想与另一台机器对话
时，下面的某个行为将会发生。

1. Bob 的电脑发送一串 1 和 0 比特值，表示电路上的高低电压。这些比特构成了一种信
息，包括请求头和消息体。 请求头包含当前 Bob 的本地路由器 MAC 地址和 Alice 的 IP初见网络爬虫 ｜ 3
地址。消息体包含 Bob 对 Alice 服务器应用的请求。

2. Bob 的本地路由器收到所有 1 和 0 比特值，把它们理解成一个数据包（packet），从 Bob
自己的 MAC 地址“寄到” Alice 的 IP 地址。他的路由器把数据包“盖上”自己的 IP 地
址作为“发件”地址，然后通过互联网发出去。

3. Bob 的数据包游历了一些中介服务器，沿着正确的物理 / 电路路径前进，到了 Alice 的
服务器。

4. Alice 的服务器在她的 IP 地址收到了数据包。

5. Alice 的服务器读取数据包请求头里的目标端口（通常是网络应用的 80 端口，可以理解
成数据包的“房间号”， IP 地址就是“街道地址”），然后把它传递到对应的应用——网
络服务器应用上。

6. 网络服务器应用从服务器处理器收到一串数据，数据是这样的：

    ♦ 这是一个 GET 请求

    ♦ 请求文件 index.html

7. 网络服务器应用找到对应的 HTML 文件， 把它打包成一个新的数据包发送给 Bob，然
后通过它的本地路由器发出去，用同样的过程回传到 Bob 的机器上。

瞧！我们就这样实现了互联网。

那么， 在这场数据交换中，网络浏览器从哪里开始参与的？完全没有参与。其实，在互联
网的历史中，浏览器是一个比较年轻的发明，始于 1990 年的 Nexus 浏览器。

的确， 网络浏览器是一个非常有用的应用，它创建信息的数据包，发送它们，然后把你获
取的数据解释成漂亮的图像、 声音、视频和文字。但是，网络浏览器就是代码，而代码是
可以分解的， 可以分解成许多基本组件，可重写、重用，以及做成我们想要的任何东西。
网络浏览器可以让服务器发送一些数据， 到那些对接无线（或有线）网络接口的应用上，
但是许多语言也都有实现这些功能的库文件。

让我们看看 Python 是如何实现的：

    from urllib.request import urlopen
    html = urlopen("http://pythonscraping.com/
                        pages/page1.html")
    print(html.read())

你可以把这段代码保存为scrapetest.py ，然后在终端里运行如下命令： 

    $python scrapetest.py 

注意，如果你的设备上安装了Python  2.x ，可能需要直接指明版本才能运行Python  3.x 

代码： 
     
    $python3 scrapetest.py 
     
这将会输出http://pythonscraping.com/pages/page1.html 这个网页的全部 HTML 代码。更
准确地说，这会输出在域名为 http://pythonscraping.com 的服务器上 < 网络应用根地址 >/
pages 文件夹里的 HTML 文件 page1.html 的源代码。

有什么区别？ 现在大多数网页需要加载许多相关的资源文件。可能是图像文件、 JavaScript
文件、 CSS 文件， 或你需要连接的其他各种网页内容。当网络浏览器遇到一个标签时，比
如\ <img src="cuteKitten.jpg">，会向服务器发起另一个请求，以获取 cuteKitten.jpg 文件
中的数据为用户充分渲染网页。但是，我们的 Python 程序没有返回并向服务器请求多个文
件的逻辑，它只能读取我们已经请求的单个 HTML 文件

那么我们应该怎样做呢？幸好 Python 语法接近正常英文，下面这行代码
   
    from urllib.request import urlopen 
   
其实已经显示了它的含义：它查找Python 的request 模块（在urllib 库里面），只导入一个 urlopen 函数。

urllib 还是urllib2 ？ 如果你用过Python 2.x 里的urllib2 库，可能会发现urllib2 与urllib 有些不同。在Python 3.x 里，urllib2 改名为urllib ，被分成一些子模块：urllib.request、urllib.parse 和urllib.error。尽管函数名称大多和原来一样，但是在用新的urllib 库时需要注意哪些函数被移动到子模块里了。 
urllib 是Python 的标准库（就是说你不用额外安装就可以运行这个例子），包含了从网 

络请求数据，处理cookie ，甚至改变像请求头和用户代理这些元数据的函数。我们将在 

本书中广泛使用urllib，所以建议你读读这个库的Python 文档（https://docs.python.org/3/library/urllib.html ）。 

urlopen 用来打开并读取一个从网络获取的远程对象。因为它是一个非常通用的库（它可 

以轻松读取HTML 文件、图像文件，或其他任何文件流），所以我们将在本书中频繁地使 

用它。 
 
> 1.2　BeautifulSoup简介

“美味的汤，绿色的浓汤， 

在热气腾腾的盖碗里装！ 

谁不愿意尝一尝，这样的好汤？ 

晚餐用的汤，美味的汤！” 

    
库的名字取自刘易斯 卡罗尔在《爱丽丝梦游仙境》里的同名诗歌。在故事 
BeautifulSoup中，这首诗是素甲鱼 唱的。


就像它在仙境中的说法一样，BeautifulSoup 尝试化平淡为神奇。它通过定位HTML 标签来 
格式化和组织复杂的网络信息，用简单易用的Python 对象为我们展现XML 结构信息。


> 1.2.1　安装BeautifulSoup 

由于BeautifulSoup 库不是Python 标准库，因此需要单独安装。在本书中，我们将使用最 

新的BeautifulSoup 4 版本（也叫BS4 ）。BeautifulSoup 4 的所有安装方法都在http://www.crummy.com/software/BeautifulSoup/bs4/doc/ 里面。Linux 系统上的基本安装方法是： 
   
    $sudo apt-get install python-bs4 
    
对于Mac 系统，首先用 
   
    $sudo easy_install pip 
    
安装 Python 的包管理器 pip，然后运行
 
    $pip install beautifulsoup4
   
来安装库文件。

另外，注意如果你的设备同时安装了Python  2.x 和Python  3.x ，你需要用python3 运行 Python 3.x ：
    
    $python3 myScript.py 
    
当你安装包的时候，如果有可能安装到了Python 2.x 而不是Python 3.x 里，就需要使用：
   
    $sudo python3 setup.py install 
    
如果用 pip 安装，你还可以用 pip3 安装 Python 3.x 版本的包：
  
    $pip3 install beautifulsoup4 
   

在Windows 系统上安装与在Mac 和Linux 上安装差不多。从上面的下载链接下载最新的BeautifulSoup 4 源代码，解压后进入文件，然后执行：
  
    >python setup.py install 
    
这样就可以了！BeautifulSoup 将被当作设备上的一个Python 库。你可以在Python 终端里导入它测试一下：
 
    $python
    >from bs4 import BeautifulSoup
   
如果没有错误，说明导入成功了

另外，还有一个 Windows 版 pip（https://pypi.python.org/pypi/setuptools） 的 .exe 格式安装器 ，装了之后你就可以轻松安装和管理包了：
  
    >pip install beautifulsoup4 
    

用虚拟环境保存库文件
如果你同时负责多个 Python 项目，或者想要轻松打包某个项目及其关联的库文件，再
或者你担心已安装的库之间可能有冲突，那么你可以安装一个 Python 虚拟环境来分而
治之。

当一个 Python 库不用虚拟环境安装的时候，你实际上是全局安装它。这通常需要有管
理员权限，或者以 root 身份安装，这个库文件对设备上的每个用户和每个项目都是存
在的。好在创建虚拟环境非常简单：

    $ virtualenv scrapingEnv

这样就创建了一个叫作 scrapingEnv 的新环境，你需要先激活它再使用：

    $ cd scrapingEnv/
    $ source bin/activate

激活环境之后，你会发现环境名称出现在命令行提示符前面，提醒你当前处于虚拟环
境中。后面你安装的任何库和执行的任何程序都是在这个环境下运行。

在新建的 scrapingEnv 环境里，可以安装并使用 BeautifulSoup：

    (scrapingEnv)ryan$ pip install beautifulsoup4
    (scrapingEnv)ryan$ python
    > from bs4 import BeautifulSoup
    >

当不再使用虚拟环境中的库时，可以通过释放命令来退出环境：

    (scrapingEnv)ryan$ deactivate
    ryan$ python
    > from bs4 import BeautifulSoup
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    ImportError: No module named 'bs4'

将项目关联的所有库单独放在一个虚拟环境里，还可以轻松打包整个环境发生给其他
人。只要他们的 Python 版本和你的相同，你打包的代码就可以直接通过虚拟环境运
行，不需要再安装任何库。初见网络爬虫 ｜ 7
尽管本书的例子都不要求你使用虚拟环境，但是请记住，你可以在任何时候激活并使
用它

> 1.2.2　运行BeautifulSoup

BeautifulSoup 库最常用的对象恰好就是 BeautifulSoup对象。让我们把本章开头的例子调整一下运行看看：

    from urllib.request import urlopen
    from bs4 import BeautifulSoup
    html = urlopen("http://www.pythonscraping.com/
                    pages/page1.html")
    bsObj = BeautifulSoup(html.read())
    print(bsObj.h1)

输出结果是：
     
    <h1>An Interesting Title</h1>
    
和前面例子一样，我们导入 urlopen，然后调用 html.read() 获取网页的 HTML 内容。这
样就可以把 HTML 内容传到 BeautifulSoup 对象，转换成下面的结构：

   
    • html → <html><head>...</head><body>...</body></html>
        — head → <head><title>A Useful Page<title></head>
            — title → <title>A Useful Page</title>
        — body → <body><h1>An Int...</h1><div>Lorem ip
                            ...</div></body>
            — h1 → <h1>An Interesting Title</h1>
            — div → <div>Lorem Ipsum dolor...</div>


可以看出，我们从网页中提取的 /<h1> 标签被嵌在 BeautifulSoup 对象 bsObj结构的第二层（html → body → h1）。但是，当我们从对象里提取 h1 标签的时候，可以直接调用它：

    bsObj.h1

其实，下面的所有函数调用都可以产生同样的结果：

    bsObj.html.body.h1
    bsObj.body.h1
    bsObj.html.h1
    
希望这个例子可以向你展示 BeautifulSoup 库的强大与简单。其实，任何 HTML（或
XML）文件的任意节点信息都可以被提取出来，只要目标信息的旁边或附近有标记就行。
在第 3 章，我们将进一步探讨一些更复杂的 BeautifulSoup 函数，还会介绍正则表达式，以及如何把正则表达式用于 BeautifulSoup 以对网站信息进行提取。

> 1.2.3　可靠的网络连接

网络是十分复杂的。网页数据格式不友好，网站服务器宕机，目标数据的标签找不到，都
是很麻烦的事情。 网络数据采集最痛苦的遭遇之一，就是爬虫运行的时候你洗洗睡了，梦
想着明天一早数据就都会采集好放在数据库里， 结果第二天醒来，你看到的却是一个因某
种数据格式异常导致运行错误的爬虫， 在前一天当你不再盯着屏幕去睡觉之后，没过一会
儿爬虫就不再运行了。 那个时候，你可能想骂发明互联网（以及那些奇葩的网络数据格
式）的人，但是你真正应该斥责的人是你自己，为什么一开始不估计可能会出现的异常！

让我们看看爬虫 import 语句后面的第一行代码，如何处理那里可能出现的异常:
   
    html = urlopen("http://www.pythonscraping.com/
                    pages/page1.html")
     
这行代码主要可能会发生两种异常：

* 网页在服务器上不存在（或者获取页面的时候出现错误）
* 服务器不存在

第一种异常发生时，程序会返回 HTTP 错误。 HTTP 错误可能是“404 Page Not Found”“500
Internal Server Error”等。所有类似情形， urlopen 函数都会抛出“HTTPError”异常。我们
可以用下面的方式处理这种异常：

    try:
        html = urlopen("http://www.pythonscraping.com/
                        pages/page1.html")
    except HTTPError as e:
        print(e)
        # 返回空值，中断程序， 或者执行另一个方案
    else:
        # 程序继续。注意： 如果你已经在上面异常捕
                        捉那一段代码里返回或中断（break），
        # 那么就不需要使用else语句了，这段代码也不会执行

如果程序返回 HTTP 错误代码，程序就会显示错误内容，不再执行 else 语句后面的代码。

如果服务器不存在（就是说链接 http://www.pythonscraping.com/ 打不开，或者是 URL 链接写错了）， urlopen会返回一个None对象。这个对象与其他编程语言中的null类似。我们可以增加一个判断语句检测返回的 html 是不是 None：
    
    if html is None:
        print("URL is not found")
    else:
        # 程序继续

当然，即使网页已经从服务器成功获取，如果网页上的内容并非完全是我们期望的那样，
仍然可能会出现异常。 每当你调用 BeautifulSoup 对象里的一个标签时，增加一个检查条件
保证标签确实存在是很聪明的做法。 如果你想要调用的标签不存在， BeautifulSoup 就会返初见网络爬虫 ｜ 9
回 None 对象。不过，如果再调用这个 None 对象下面的子标签，就会发生 AttributeError
错误。

下面这行代码（nonExistentTag 是虚拟的标签， BeautifulSoup 对象里实际没有）

    print(bsObj.nonExistentTag)

会返回一个 None 对象。处理和检查这个对象是十分必要的。如果你不检查，直接调用这个
None 对象的子标签，麻烦就来了。如下所示
   
    print(bsObj.nonExistentTag.someTag)

这时就会返回一个异常：
 
    AttributeError: 'NoneType' object has no attribute 'someTag'

那么我们怎么才能避免这两种情形的异常呢？最简单的方式就是对两种情形进行检查：
   
    try:
        badContent = bsObj.nonExistingTag.anotherTag
    except AttributeError as e:
        print("Tag was not found")
    else:
        if badContent == None:
            print ("Tag was not found")
        else:
            print(badContent)
     

初看这些检查与错误处理的代码会觉得有点儿累赘，但是，我们可以重新简单组织一下代
码，让它变得不那么难写（更重要的是，不那么难读）。例如，下面的代码是上面爬虫的
另一种写法：
    
    from urllib.request import urlopen
    from urllib.error import HTTPError
    from bs4 import BeautifulSoup
        def getTitle(url):
            try:
                html = urlopen(url)
            except HTTPError as e:
                return None
            try:
                bsObj = BeautifulSoup(html.read())
                title = bsObj.body.h1
            except AttributeError as e:
                return None
            return title
    title = getTitle("http://www.pythonscraping.com/
                                    pages/page1.html")
    if title == None:
        print("Title could not be found")
    else:
        print(title)
     

在这个例子中，我们创建了一个 getTitle 函数，可以返回网页的标题，如果获取网页
的时候遇到问题就返回一个 None 对象。在 getTitle 函数里面，我们像前面那样检查了
HTTPError，然后把两行 BeautifulSoup 代码封装在一个 try 语句里面。这两行中的任何一
行有问题， AttributeError 都可能被抛出（如果服务器不存在， html 就是一个 None 对象，
html.read() 就会抛出 AttributeError）。其实，我们可以在 try 语句里面放任意多行代码，
或者放一个在任意位置都可以抛出 AttributeError 的函数。

在写爬虫的时候， 思考代码的总体格局，让代码既可以捕捉异常又容易阅读，这是很重要
的。如果你还希望能够很大程度地重用代码， 那么拥有像 getSiteHTML 和 getTitle 这样的
通用函数（具有周密的异常处理功能）会让快速稳定地网络数据采集变得简单易行。
           
           
           `
        };
    }
    render() {
        return (
            <div >
                {/* 111111111111111
                <Py112 /> */}
                <Highlight className='atelier-dune-dark'>
                    <ReactMarkdown source={this.state.value}/>
                </Highlight>
                {/* <MarkdownPreview value={this.state.value}/> */}
            </div>
        )
    }
}

export default Py11